package mark.tom.hawk;

import androidx.annotation.NonNull;
import android.util.Log;

import java.util.Map;
import java.util.Set;

import mark.tom.hawk.template.Converter;
import mark.tom.hawk.template.Encryption;
import mark.tom.hawk.template.Facade;
import mark.tom.hawk.template.Logger;
import mark.tom.hawk.template.Serializer;
import mark.tom.hawk.template.Storage;

import static mark.tom.hawk.C.DEFAULT_SP_TAG;

class HawkFacade implements Facade {

    private final Map<String, Storage> storage;
    private final Converter converter;
    private final Encryption encryption;
    private final Serializer serializer;
    private final Logger logger;

    HawkFacade(HawkBuilder builder) {
        encryption = builder.getEncryption();
        storage = builder.getStorage();
        converter = builder.getConverter();
        serializer = builder.getSerializer();
        logger = builder.getLogger();
    }

    @Override
    public <T> boolean put(@NonNull String key, @NonNull T value) {
        return put(key, value, DEFAULT_SP_TAG);
    }

    @Override
    public <T> boolean put(@NonNull String key, @NonNull T value, @NonNull String... tags) {
        log("===================================================================================\n", Log.DEBUG);
        long startTime = System.currentTimeMillis();
        String serializedText = null;
        try {
            serializedText = getPositiveProcessedData(key, value);
        } catch (Exception ex) {
            log(ex.getMessage(), Log.ERROR);
        }
        boolean result = false;
        if (null != serializedText) {
            result = saveEncryptRecord(key, serializedText, tags);
        }
        log("Put Method Cost Time is :" + (System.currentTimeMillis() - startTime) + "ms");
        log("===================================================================================\n", Log.DEBUG);
        return result;
    }

    @Override
    public <T> T get(@NonNull String key) {
        return get(key, DEFAULT_SP_TAG);
    }

    @Override
    public <T> T get(@NonNull String key, @NonNull String tag) {
        log("===================================================================================\n", Log.DEBUG);
        long startTime = System.currentTimeMillis();
        T result = null;
        try {
            String serializedText = obtainEncryptRecord(key, tag);
            result = getReverseProcessedData(key, serializedText);
        } catch (Exception ex) {
            log(ex.getMessage(), Log.ERROR);
        }
        log("Get Method Cost Time is :" + (System.currentTimeMillis() - startTime) + "ms");
        log("===================================================================================\n", Log.DEBUG);
        return result;
    }

    @Override
    public long count() {
        return count(DEFAULT_SP_TAG);
    }

    @Override
    public long count(@NonNull String tag) {
        long result = 0;
        Storage tmp = storage.get(tag);
        if (null != tmp) {
            result = tmp.count();
        }
        return result;
    }

    @Override
    public boolean deleteAll() {
        return deleteAll(DEFAULT_SP_TAG);
    }

    @Override
    public boolean deleteAll(@NonNull String tag) {
        boolean result = false;
        Storage tmp = storage.get(tag);
        if (null != tmp) {
            result = tmp.deleteAll();
        }
        return result;
    }

    @Override
    public boolean delete(@NonNull String key) {
        return delete(key, DEFAULT_SP_TAG);
    }

    @Override
    public boolean delete(@NonNull String key, @NonNull String tag) {
        boolean result = false;
        Storage tmp = storage.get(tag);
        if (null != tmp) {
            result = tmp.delete(key);
        }
        return result;
    }

    @Override
    public boolean contains(@NonNull String key) {
        return contains(key, DEFAULT_SP_TAG);
    }

    @Override
    public boolean contains(@NonNull String key, @NonNull String tag) {
        boolean result = false;
        Storage tmp = storage.get(tag);
        if (null != tmp) {
            result = tmp.contains(key);
        }
        return result;
    }

    @Override
    public <T> String getPositiveProcessedData(@NonNull String key, @NonNull T value) {
        // Validate
        Util.checkNull("Hawk.put -> null key, returning null value", key);
        Util.checkNull("Hawk.put -> Value is null. Any existing value will be deleted with the given key", value);
        log("Hawk.put -> key: " + key + ", value: " + value);

        if (Map.class.isAssignableFrom(value.getClass())
                || Set.class.isAssignableFrom(value.getClass())) {
            throw new IllegalArgumentException("I don't want to support these kinds, For Now");
        }

        // 1. Convert to text
        String plainText = converter.toString(value);
        log("Hawk.put -> Converted to " + plainText);
        Util.checkNull("Hawk.put -> Converter failed", plainText);

        // 2. Encrypt the text
        String cipherText = null;
        try {
            cipherText = encryption.encrypt(key, plainText);
            log("Hawk.put -> Encrypted to  " + cipherText);
        } catch (Exception e) {
            log(e.getMessage(), Log.ERROR);
        }
        Util.checkNull("Hawk.put -> Encryption failed", cipherText);

        // 3. Serialize the given object along with the cipher text
        String serializedText = serializer.serialize(cipherText, value);
        log("Hawk.put -> Serialized to  " + serializedText);
        Util.checkNull("Hawk.put -> Serialization failed", serializedText);
        return serializedText;
    }

    @Override
    public <T> T getReverseProcessedData(@NonNull String key, @NonNull String serializedText) {
        Util.checkNull("Hawk.get -> null key, returning null value", key);
        Util.checkNull("Hawk.get -> Fetching from storage failed", serializedText);
        log("Hawk.get -> Fetched from storage : " + serializedText);

        // 2. Deserialize
        DataInfo dataInfo = serializer.deserialize(serializedText);
        log("Hawk.get -> Deserialized");
        Util.checkNull("Hawk.get -> Deserialization failed", dataInfo);

        // 3. Decrypt
        String plainText = null;
        try {
            plainText = encryption.decrypt(key, dataInfo.cipherText);
            log("Hawk.get -> Decrypted to : " + plainText);
        } catch (Exception e) {
            log("Hawk.get -> Decrypt failed: " + e.getMessage());
        }
        Util.checkNull("Hawk.get -> Decrypt failed", plainText);

        // 4. Convert the text to original data along with original type
        T result = null;
        try {
            result = converter.fromString(plainText, dataInfo);
            log("Hawk.get -> Converted to : " + result);
        } catch (Exception e) {
            log("Hawk.get -> Converter failed");
        }
        return result;
    }

    @Override
    public boolean saveEncryptRecord(@NonNull String key, @NonNull String encryptRecord) {
        return saveEncryptRecord(key, encryptRecord, DEFAULT_SP_TAG);
    }

    @Override
    public boolean saveEncryptRecord(@NonNull String key, @NonNull String encryptRecord, @NonNull String... tags) {
        // 4. Save to the storage
        boolean result = false;
        for (String tag : tags) {
            Storage tmp = storage.get(tag);
            if (null != tmp) {
                boolean tmpResult = tmp.put(key, encryptRecord);
                if (!tmpResult) {
                    log("Hawk.put -> " + tag + " Store operation failed", Log.ERROR);
                } else {
                    log("Hawk.put -> " + tag + " Stored successfully");
                }
                result = result || tmpResult;
            }
        }
        if (result) {
            log("Hawk.put -> Stored successfully");
        } else {
            log("Hawk.put -> Store operation failed", Log.ERROR);
        }
        return result;
    }

    @Override
    public String obtainEncryptRecord(@NonNull String key) {
        return obtainEncryptRecord(key, DEFAULT_SP_TAG);
    }

    @Override
    public String obtainEncryptRecord(@NonNull String key, @NonNull String tag) {
        log("Hawk.get -> key: " + key);
        String result = null;
        // 1. Get serialized text from the storage
        Storage tmp = storage.get(tag);
        if (null == tmp) {
            log("Hawk.get -> Can't find storage failed", Log.ERROR);
        } else {
            result = tmp.get(key);
        }
        return result;
    }

    private void log(String message) {
        logger.log(message, Log.INFO);
    }

    private void log(String message, int level) {
        logger.log(message, level);
    }

    @Override
    public boolean hasBuild() {
        return true;
    }

    @Override
    public void destroy() {
    }
}
